﻿using System;
using ParserGenerator.Collections;
using ParserGenerator.Exceptions;
using ParserGenerator.GrammarConstruction;
using ParserGenerator.Input;
using ParserGenerator.Output;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.CodeGeneration
{
    [Serializable]
    public class CSharpExpressionWriter : CSharpSemanticActionWriter
    {
        public Identifier ResultValue { get; }
        public Identifier ResultContext { get; }
        public Identifier SlotValues { get; }
        public Identifier SlotContexts { get; }
        public GrammarProduction Production { get; internal set; }

        internal CSharpExpressionWriter(GrammarDeclaration grammar)
            : base(grammar)
        {
            ResultValue = Identifier.New(grammar.GetOptionValue("pg.reduce.result.value", "result"));
            ResultContext = Identifier.New(grammar.GetOptionValue("pg.reduce.result.context", "_nodeContext"));
            SlotValues = Identifier.New(grammar.GetOptionValue("pg.reduce.slot.values", "_values"));
            SlotContexts = Identifier.New(grammar.GetOptionValue("pg.reduce.slot.contexts", "_contexts"));
        }

        public virtual void CheckActionContext(SourceContext context)
        {
            if (Production == null)
            {
                SyntaxErrorException e = new SyntaxErrorException(SR.InvalidSemanticActionContext);
                e.AddLocation(context);
                throw e;
            }
        }

        public override void WriteResultValueReferenceExpression(ResultValueReferenceExpression n, IndentedWriter writer, int d)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            CheckActionContext(n.Context);
            WriteIdentifier(ResultValue, writer);
        }

        public override void WriteResultContextReferenceExpression(ResultContextReferenceExpression n, IndentedWriter writer, int d)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            CheckActionContext(n.Context);
            WriteIdentifier(ResultContext, writer);
        }

        public virtual int CheckSlotNumber(int num, SourceContext context)
        {
            if (num <= 0)
            {
                SyntaxErrorException e = new SyntaxErrorException(SR.SlotNumberMustBePositive);
                e.AddLocation(context);
                throw e;
            }
            if (num > Production.Length)
            {
                SyntaxErrorException e = new SyntaxErrorException(SR.SlotNumberOutOfRange);
                e.AddLocation(context);
                throw e;
            }
            return Production.Length - num + 1;
        }

        public virtual void CheckSlotType(GrammarSymbol sym, SourceContext context)
        {
            if (sym == null)
                throw new ArgumentNullException(nameof(sym));
            if (!sym.HasSemanticValue)
            {
                string message = string.Format(SR.GrammarSymbolHasNoSemanticValue, sym.Name);
                SyntaxErrorException e = new SyntaxErrorException(message);
                e.AddLocation(sym.Context);
                e.AddLocation(context);
                throw e;
            }
        }

        public override void WriteSlotValueReferenceExpression(SlotValueReferenceExpression n, IndentedWriter writer, int d)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            CheckActionContext(n.Context);
            int offset = CheckSlotNumber(n.Slot.AsNativeInt, n.Context);
            GrammarSymbol sym = Production.Symbols[n.Slot.AsNativeInt - 1];
            CheckSlotType(sym, n.Context);
            Expression values = Expression.NewNameReference(SlotValues);
            Expression count = Expression.NewAccess(values, Identifier.New("Count"));
            Expression index = Expression.NewSubtract(count, Expression.NewInteger(IntegerLiteral.New(offset)));
            Expression value = Expression.NewSubscript(values, SubscriptList.New(Sequence.Create(Subscript.NewIndex(index)), false));
            Expression cast = Expression.NewAsInstanceOf(value, sym.Type);
            WriteExpression(cast, writer, d);
        }

        public override void WriteSlotContextReferenceExpression(SlotContextReferenceExpression n, IndentedWriter writer, int d)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            CheckActionContext(n.Context);
            int offset = CheckSlotNumber(n.Slot.AsNativeInt, n.Context);
            Expression contexts = Expression.NewNameReference(SlotContexts);
            Expression count = Expression.NewAccess(contexts, Identifier.New("Count"));
            Expression index = Expression.NewSubtract(count, Expression.NewInteger(IntegerLiteral.New(offset)));
            Expression context = Expression.NewSubscript(contexts, SubscriptList.New(Sequence.Create(Subscript.NewIndex(index)), false));
            WriteExpression(context, writer, d);
        }
    }
}
